#pragma once
#include <algorithm>
#include <cassert>
#include <cmath>
#include <numeric>
#include <vector>
#include <boost/function.hpp>
#include <boost/lambda/lambda.hpp>
#include "core.h"

namespace linal
{
    class Vector
    {
        DISALLOW_COPY_AND_ASSIGN(Vector);

    public:
        // Создаёт вектор нулевой размерности.
        // Линейная алгебра не оперирует таким понятием как вектор нулевой размерности, однако при создании объекта класса
        // Вектор информация о его размерности может быть еще неизвестной. Т.о. в данной библиотеке вектор нулевой размерности
        // просто является особым случаем вектора в n-мерном вещественном пространстве. Все классы и функции библиотеки будут
        // корректно работать с вектором нулевой размерности.
        Vector(void) :
            mComponents(0)
        {
        }

        // Создаёт n-мерный вектор. Значение каждой компоненты вектора выисляется по её индексу (посредством вызова функтора с
        // индексом компоненты в качестве входного параметра).
        Vector(size_t dimension, boost::function<double(size_t)> functor) :
            mComponents(dimension)
        {
            for(size_t i = 0; i < dimension; ++i)
                mComponents[i] = functor(i);
        }

        // Предоставляет доступ к компоненте вектора для выполнения операций чтения/записи.
        double& operator[](size_t i)
        {
            assert(i < dimension());
            return mComponents[i];
        }

        // Предоставляет доступ к компоненте вектора для выполнения операции чтения.
        const double& operator[](size_t i) const
        {
            assert(i < dimension());
            return mComponents[i];
        }

        // Позволяет получить размерность вектора.
        size_t dimension() const {return mComponents.size();}

        // Изменяет размерность вектора.
        // Если новая размерность вектора больше чем старая размерность, то добавленным компонентам присваивается
        // нулевое значение.
        void resize(size_t dimension) {mComponents.resize(dimension, 0.);}

        // Присваивает всем компонентам вектора нулевое значение.
        void zeroize() {std::generate(begin(), end(), boost::lambda::constant(0.));}

        // Копирует значения компонент одного вектора в компоненты другого вектора.
        // Предполагается, что оба вектора имеют одинаковую размерность.
        static void copy(const Vector& source, Vector& destination)
        {
            assert(source.dimension() == destination.dimension());
            std::copy(source.begin(), source.end(), destination.begin());
        }

        // Вычисляет сумму двух векторов.
        // Предполагается, что все вектора имеют одинаковую размерность.
        static void add(const Vector& summand1, const Vector& summand2, Vector& sum)
        {
            assert(summand1.dimension() == summand2.dimension());
            assert(summand1.dimension() == sum.dimension());
            std::transform(summand1.begin(), summand1.end(), summand2.begin(), sum.begin(), std::plus<double>());
        }

        // Вычисляет разность двух векторов.
        // Предполагается, что все вектора имеют одинаковую размерность.
        static void subtract(const Vector& minuend, const Vector& subtrahend, Vector& difference)
        {
            assert(minuend.dimension() == subtrahend.dimension());
            assert(minuend.dimension() == difference.dimension());
            std::transform(minuend.begin(), minuend.end(), subtrahend.begin(), difference.begin(), std::minus<double>());
        }

        // Вычисляет скалярное произведение двух векторов.
        // Предполагается, что оба вектора имеют одинаковую размерность.
        static double dotProduct(const Vector& v1, const Vector& v2)
        {
            assert(v1.dimension() == v2.dimension());
            return std::inner_product(v1.begin(), v1.end(), v2.begin(), 0.);
        }

        // Вычисляет евклидову норму вектора.
        double norm() const {return std::sqrt(normSquared());}

        // Вычисляет квадрат евклидовой нормы вектора.
        // В ряде случаев необходимо знать значение не самой нормы вектора, а её квадрата. Поскольку евклидова норма, в свою
        // очередь, является корнем из скалярного произведения вектора на себя, а оперции возведения в квадрат и, тем более,
        // извлечения корня ресурсоёмки, будет целесообразно иметь возможность вычислять квадрат нормы вектора напрямую.
        double normSquared() const {return dotProduct(*this, *this);}

        // Набор итераторов, позволяющий применять к объектам класса Vector stl-алгоритмы.
        std::vector<double>::iterator end() {return mComponents.end();}
        std::vector<double>::iterator begin() {return mComponents.begin();}
        std::vector<double>::const_iterator end() const {return mComponents.end();}
        std::vector<double>::const_iterator begin() const {return mComponents.begin();}

    private:
        // Компоненты вектора.
        std::vector<double> mComponents;
    };
}
